﻿using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;

using EnvDTE80;

using log4net;

using MahApps.Metro;
using MahApps.Metro.Controls;

using VSTalk.Engine;
using VSTalk.Engine.Core.EnvironmentManagers.UI;
using VSTalk.Logger;
using VSTalk.Tools;

namespace Microsoft.VSTalk.Integration
{
    /// <summary>
    /// A helper class for managing the active theme.
    /// </summary>
    public class ThemeManager : IThemeManager
    {
        private static readonly ILog _log = LoggerFactory.Create(typeof(VSTalkPackage));

        private readonly VSTalkPackage _package;

        /// <summary>
        /// The singleton instance of the <see cref="ThemeManager"/> class.
        /// </summary>
        private static ThemeManager _instance;

        private static IWindowManager WindowManager
        {
            get { return AppServices.Get<IWindowManager>(); }
        }

        private static IHostedControls HostedControls
        {
            get { return WindowManager.HostedControls; }
        }

        /// <summary>
        /// Gets an instance of the <see cref="ThemeManager"/> class.
        /// </summary>
        /// <param name="package">The hosting package.</param>
        /// <returns>An instance of the <see cref="ThemeManager"/> class.</returns>
        internal static ThemeManager GetInstance(VSTalkPackage package)
        {
            return _instance ?? (_instance = new ThemeManager(package));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ThemeManager"/> class.
        /// </summary>
        /// <param name="package">The hosting package.</param>
        private ThemeManager(VSTalkPackage package)
        {
            _package = package;
            Init();
        }

        private ThemeMode _activeTheme;

        /// <summary>
        /// Gets the active theme.
        /// </summary>
        public ThemeMode ActiveTheme
        {
            get { return _activeTheme; }
            private set
            {
                if (_activeTheme != value)
                {
                    _activeTheme = value;
                    PropertyChanged.Notify(() => ActiveTheme);
                }
            }
        }

        /// <summary>
        /// Applies the appropriate theme based on current settings.
        /// </summary>
        public void ApplyTheme()
        {
            _log.Info("Start applying theme");

            _log.Info("Resolving active theme");
            ActiveTheme = ResolveActiveTheme();
            _log.Info("Active theme is " + ActiveTheme);
            
            var controls = HostedControls.All
                .Select(VisualHelper.SearchParent<MetroContentControl>)
                .Where(control => control != null);
            
            var windows = VisualHelper.SearchControls<MetroWindow>().ToList();
            
            var all = controls.OfType<FrameworkElement>().Concat(windows);
            foreach (var frameworkElement in all)
            {
                var target = frameworkElement.Resources;
                foreach (var dictionary in target.MergedDictionaries)
                {
                    ApplyThemeToElement(dictionary, ActiveTheme);
                }
            }

            _log.Info("Theme has been applied");
        }

        public void Init()
        {
            ActiveTheme = ResolveActiveTheme();
        }

        /// <summary>
        /// Resolves which theme should currently be active based on settings.
        /// </summary>
        /// <returns>The resolved theme.</returns>
        private ThemeMode ResolveActiveTheme()
        {
            var theme = (ThemeMode)Settings.Default.General_Theme;

            return theme == ThemeMode.AutoDetect ? AutoDetectTheme() : theme;
        }

        /// <summary>
        /// Auto-detects which theme should be active based on the current IDE settings.
        /// </summary>
        private ThemeMode AutoDetectTheme()
        {
            const int medianColor = 128 * 3;
            var bgColor = GetColorFromUInt(_package.IDE.GetThemeColor(vsThemeColors.vsThemeColorToolWindowBackground));

            return (bgColor.R + bgColor.G + bgColor.B) >= medianColor ? ThemeMode.Light : ThemeMode.Dark;
        }

        /// <summary>
        /// A simple converter for turning a <see cref="uint"/> into a <see cref="Color"/>.
        /// </summary>
        /// <param name="number">The number to convert.</param>
        /// <returns>The color.</returns>
        private static Color GetColorFromUInt(uint number)
        {
            return Color.FromRgb((byte)(number >> 16),
                                 (byte)(number >> 8),
                                 (byte)(number >> 0));
        }

        /// <summary>
        /// Applies the specified theme to the specified element.
        /// </summary>
        /// <param name="element">The element to theme.</param>
        /// <param name="theme">The theme to apply.</param>
        private void ApplyThemeToElement(ResourceDictionary element, ThemeMode theme)
        {
            if (element == null) return;

            var mahTheme = theme == ThemeMode.Dark ? Theme.Dark : Theme.Light;
            var targetAccent = MahApps.Metro.ThemeManager.DefaultAccents.First(accent => accent.Name == "Blue");
            MahApps.Metro.ThemeManager.ChangeTheme(element, targetAccent, mahTheme);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}